//
//  MengLiao
//
//  Created by xiuxin on 2022/7/15.
//  Copyright © 2022 HZMengLiao. All rights reserved.
//


#import "KCycleView.h"
#import <objc/runtime.h>

static const NSInteger KCycleInvalideIndex = -1;

@interface UIView(KCyclePage)

@property (nonatomic) NSInteger khPageIndex;

@property (nonatomic) NSInteger khScrollIndex;

@end

@implementation UIView(KCyclePage)

- (NSInteger)khPageIndex
{
    NSNumber * index = objc_getAssociatedObject(self, _cmd);
    return [index integerValue] - 1;
}

- (void)setKhPageIndex:(NSInteger)index
{
    objc_setAssociatedObject(self, @selector(khPageIndex), [NSNumber numberWithInteger:index + 1], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSInteger)khScrollIndex
{
    NSNumber * index = objc_getAssociatedObject(self, _cmd);
    return [index integerValue] - 1;
}

- (void)setKhScrollIndex:(NSInteger)index
{
    objc_setAssociatedObject(self, @selector(khScrollIndex), [NSNumber numberWithInteger:index + 1], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

@implementation KCycleViewLayout

- (instancetype)initWithInsets:(UIEdgeInsets)insets margin:(CGFloat)margin pageSize:(CGSize)pageSize loopEnabled:(BOOL)loopEnabled
{
    if (self = [super init])
    {
        _insets = insets;
        _margin = margin;
        _pageSize = pageSize;
        _loopEnabled = loopEnabled;
    }
    return self;
}

- (void)configPageFrameInScrollSize:(CGSize)scrollSize
{
    if (self.flexLayout)
    {
        if (self.vertical)
        {
            _pageSize.width = scrollSize.width;
        }
        else
        {
            _pageSize.height = scrollSize.height;
        }
    }
    CGRect pageRect = CGRectMake(_insets.left, _insets.top, scrollSize.width - _insets.left - _insets.right, scrollSize.height - _insets.top - _insets.bottom);
    if (self.vertical)
    {
        pageRect.size.height -= self.margin;
    }
    else
    {
        pageRect.size.width -= self.margin;
    }
    CGPoint center = CGPointMake(CGRectGetMidX(pageRect), CGRectGetMidY(pageRect));
    _pageFrame = CGRectMake(center.x - _pageSize.width / 2, center.y - _pageSize.height / 2, _pageSize.width, _pageSize.height);
}

- (void)setMargin:(CGFloat)margin
{
    _margin = roundf(margin);
}

@end

@interface KCycleView(KCycleViewDataSource)
- (BOOL)loopEnabled;
@end

@interface KCycleViewDataSource : NSObject

@property (weak,nonatomic) KCycleView * cycleView;

@property (weak,nonatomic) id delegate;

@property (nonatomic) NSInteger totalCount;

@property (strong,nonatomic) NSMutableArray<UIView *> * localPageList;

@property (strong,nonatomic) NSMutableArray<UIView *> * cachedPageList;

@end

@implementation KCycleViewDataSource

- (id)init
{
    self = [super init];
    if (self)
    {
        _cachedPageList = [NSMutableArray array];
    }
    return self;
}

- (void)clearData
{
    _totalCount = 0;
    [self cacheLeftLocalPages];
    [_cachedPageList makeObjectsPerformSelector:@selector(removeFromSuperview)];
    [_cachedPageList removeAllObjects];
}

- (void)loadDelegate:(id)delegate
{
    _delegate = delegate;
    _totalCount = [delegate numberOfPagesInCycleView:_cycleView];
    [self cacheLeftLocalPages];
    [self checkCacheSize];
}

- (void)makePageFrameInvalide
{
    for (UIView * page in _localPageList)
    {
        page.khScrollIndex = KCycleInvalideIndex;
    }
}

- (NSArray<UIView *> *)configPageListForIndex:(NSInteger)index offset:(NSInteger *)offset
{
    if (_totalCount == 0)
    {
        *offset = 0;
        return nil;
    }
    NSMutableArray<NSNumber *> * indexList = nil;
    if (self.cycleView.loopEnabled)
    {
        *offset = 1;
        indexList = [NSMutableArray array];
        [indexList addObject:@([self valideIndexForIndex:index - 1])];
        [indexList addObject:@([self valideIndexForIndex:index])];
        [indexList addObject:@([self valideIndexForIndex:index + 1])];
    }
    else
    {
        if (index == 0)
        {
            *offset = 0;
            indexList = [self indexListForRange:NSMakeRange(0, MIN(_totalCount, 3))];
        }
        else if (index == _totalCount - 1)
        {
            *offset = MIN(2,index);
            NSInteger count = MIN(_totalCount, 3);
            indexList = [self indexListForRange:NSMakeRange(_totalCount - count, count)];
        }
        else
        {
            indexList = [self indexListForRange:NSMakeRange(index - 1, 3)];
            *offset = 1;
        }
    }
    [self cacheLocalPages:indexList];
    NSMutableArray<UIView *> * localPageList = [NSMutableArray arrayWithCapacity:indexList.count];
    for (NSNumber * index in indexList)
    {
        [localPageList addObject:[self requestPageAtIndex:[index integerValue]]];
    }
    [self cacheLeftLocalPages];
    [self checkCacheSize];
    _localPageList = localPageList;
    return _localPageList;
}

- (NSMutableArray *)indexListForRange:(NSRange)range
{
    NSMutableArray * indexList = [NSMutableArray arrayWithCapacity:range.length];
    for (NSInteger index = 0; index < range.length; index++)
    {
        [indexList addObject:@(range.location + index)];
    }
    return indexList;
}

- (NSInteger)valideIndexForIndex:(NSInteger)index
{
    if (index < 0)
    {
        index = _totalCount - 1;
    }
    else if (index >= _totalCount)
    {
        index = 0;
    }
    return index;
}

- (UIView *)requestPageAtIndex:(NSInteger)index
{
    for (UIView * page in _localPageList)
    {
        if (page.khPageIndex == index)
        {
            [_localPageList removeObject:page];
            return page;
        }
    }
    UIView * page = [self.delegate cycleView:_cycleView pageAtIndex:index reusePage:[self dequeueReusedPageForIndex:index]];
    if (self.cycleView.layout.flexLayout)
    {
        page.autoresizingMask = self.cycleView.layout.vertical ? UIViewAutoresizingFlexibleWidth : UIViewAutoresizingFlexibleHeight;
    }
    page.khPageIndex = index;
    if (self.cycleView.layout.pageCornerRadius > 0)
    {
        page.layer.masksToBounds = YES;
        page.layer.cornerRadius = self.cycleView.layout.pageCornerRadius;
    }
    return page;
}

- (UIView *)pageAtIndex:(NSInteger)index
{
    for (UIView * page in _localPageList)
    {
        if (page.khPageIndex == index)
        {
            return page;
        }
    }
    return nil;
}

- (void)cacheLeftLocalPages
{
    for (UIView * page in _localPageList)
    {
        [self cachePage:page];
    }
    _localPageList = nil;
}

- (void)cacheLocalPages:(NSArray<NSNumber *> *)indexList
{
    NSMutableArray<UIView *> * localPageList = [NSMutableArray array];
    for (NSNumber * index in indexList)
    {
        for (UIView * page in _localPageList)
        {
            if ([@(page.khPageIndex) isEqualToNumber:index] && ![localPageList containsObject:page])
            {
                [localPageList addObject:page];
            }
        }
    }
    for (UIView * page in _localPageList)
    {
        if (![localPageList containsObject:page])
        {
            [self cachePage:page];
        }
    }
    _localPageList = localPageList;
}

- (UIView *)dequeueReusedPageForIndex:(NSInteger)index
{
    [_cachedPageList sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        return [@(labs([obj2 khPageIndex] - index)) compare:@(labs([obj1 khPageIndex] - index))];
    }];
    UIView * page = [_cachedPageList lastObject];
    [_cachedPageList removeLastObject];
    return page;
}

- (void)cachePage:(UIView *)page
{
    page.khScrollIndex = KCycleInvalideIndex;
    page.hidden = YES;
    [_cachedPageList addObject:page];
}

- (void)checkCacheSize
{
    NSInteger cacheSize = self.cycleView.maxPageInMemory;
    if (_cachedPageList.count > cacheSize)
    {
        [_cachedPageList sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
            return [@(labs([obj1 khPageIndex] - _cycleView.currentIndex)) compare:@(labs([obj2 khPageIndex] - _cycleView.currentIndex))];
        }];
        while (_cachedPageList.count > cacheSize)
        {
            UIView * page = [_cachedPageList lastObject];
            [page removeFromSuperview];
            [_cachedPageList removeLastObject];
        }
    }
}

@end

@interface KCycleView()<UIScrollViewDelegate,CAAnimationDelegate>

@property (strong,nonatomic) KCycleViewDataSource * dataSource;

@property (strong,nonatomic) NSTimer * autoTimer;

@property (nonatomic) NSInteger currentScrollIndex;

@property (strong,nonatomic) NSDate * clickTime;

@property (strong,nonatomic) UIImageView * effectImgView;

@property (nonatomic) BOOL openFadeEffect;

@property (nonatomic,assign) BOOL scrollEnable;

@end

@implementation KCycleView

- (instancetype)initWithFrame:(CGRect)frame layout:(KCycleViewLayout *)layout
{
    if (self = [super initWithFrame:frame])
    {
        _layout = layout;
        _scrollEnable = YES;
        _maxPageInMemory = 3;
        _dataSource = [KCycleViewDataSource new];
        _dataSource.cycleView = self;
        [self createUI];
        [self setNeedsDisplay];
    }
    return self;
}

- (void)createUI
{
    if (self.layout.vertical)
    {
        _scroll = [[UIScrollView alloc] initWithFrame: CGRectMake(0, 0, self.width, ceilf(self.height + self.layout.margin))];
    }
    else
    {
        _scroll = [[UIScrollView alloc] initWithFrame: CGRectMake(0, 0, ceilf(self.width + self.layout.margin), self.height)];
    }
    KDisableInsetAdjustment(_scroll);
    _scroll.alwaysBounceVertical = NO;
    _scroll.pagingEnabled = YES;
    _scroll.directionalLockEnabled = YES;
    _scroll.showsHorizontalScrollIndicator = NO;
    _scroll.showsVerticalScrollIndicator = NO;
    _scroll.scrollsToTop = NO;
    if (!self.layout.disableClick)
    {
        UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(userClicked:)];
        [_scroll addGestureRecognizer:tapGesture];
    }
    [_scroll.panGestureRecognizer addTarget:self action:@selector(scrollPan:)];
    [self addSubview:_scroll];
    [self.layout configPageFrameInScrollSize:_scroll.size];
    
    _effectImgView = [UIImageView new];
    if (_layout.pageCornerRadius > 0)
    {
        _effectImgView.layer.masksToBounds = YES;
        _effectImgView.layer.cornerRadius = _layout.pageCornerRadius;
    }
    [self addSubview:_effectImgView];
}

- (void)setScrollEnable:(BOOL)enable
{
    [_scroll setScrollEnabled:enable];
    _scrollEnable = enable;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    /*
     直接拖动UISlider，此时touch时间在150ms以内，UIScrollView会认为是拖动自己，从而拦截了event，导致UISlider接受不到滑动的event。但是只要按住UISlider一会再拖动，此时此时touch时间超过150ms，因此滑动的event会发送到UISlider上。
     */
    UIView *view = [super hitTest:point withEvent:event];
    if (self.hasRightRefresh)//商品详情有左刷新 数量为1的时候 也需要 滚动
    {
        self.scrollEnable = YES;
    }
    else
    {
        //如果响应view是UISlider,则scrollview禁止滑动
        BOOL scrollAble = ![view isKindOfClass:[UISlider class]] && (self.dataSource.totalCount > 1 || self.loopEnabled);
        self.scroll.scrollEnabled = _scrollEnable? scrollAble:NO;
    }
    return view;
}

- (void)userClicked:(UITapGestureRecognizer *)gesture
{
    NSDate * now = [NSDate date];
    if (_clickTime && [now timeIntervalSinceDate:_clickTime] < 0.5)
    {
        return;
    }
    _clickTime = now;
    if (self.dataSource.totalCount > 0 && [self.delegate respondsToSelector:@selector(cycleView:didClickPage:atIndex:)])
    {
        for (UIView * page in _dataSource.localPageList)
        {
            if (self.layout.vertical)
            {
                if (_scroll.contentOffset.y < page.centerY && _scroll.contentOffset.y + _scroll.height > page.centerY)
                {
                    [self.delegate cycleView:self didClickPage:page atIndex:page.khPageIndex];
                    break;
                }
            }
            else if (_scroll.contentOffset.x < page.centerX && _scroll.contentOffset.x + _scroll.width > page.centerX)
            {
                [self.delegate cycleView:self didClickPage:page atIndex:page.khPageIndex];
                break;
            }
        }
    }
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    if (self.layout.vertical)
    {
        _scroll.frame = CGRectMake(0, 0, self.width, ceilf(self.height + self.layout.margin));
    }
    else
    {
        _scroll.frame = CGRectMake(0, 0, ceilf(self.width + self.layout.margin), self.height);
    }
    [self.layout configPageFrameInScrollSize:_scroll.size];
    [self.dataSource makePageFrameInvalide];
    _effectImgView.frame = self.layout.pageFrame;
    _effectImgView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}

- (void)reloadData
{
    [self reloadDataAtIndex:_currentIndex];
}

- (void)reloadDataAtIndex:(NSInteger)index
{
    [_dataSource loadDelegate:_delegate];
    _scroll.delegate = self;
    [self refreshContentSize];
    [self loadIndex:[self.dataSource valideIndexForIndex:index]];
    BOOL scrollAble = self.dataSource.totalCount > 1 || self.loopEnabled;
    _scroll.scrollEnabled =_scrollEnable?scrollAble:NO;
}

- (void)clearData
{
    [_dataSource clearData];
}

- (void)refreshContentSize
{
    if (self.layout.vertical)
    {
        _scroll.contentSize = CGSizeMake(_scroll.width , _scroll.height * (self.loopEnabled ? 3 : MIN(3, _dataSource.totalCount)));
    }
    else
    {
        _scroll.contentSize = CGSizeMake(_scroll.width * (self.loopEnabled ? 3 : MIN(3, _dataSource.totalCount)), _scroll.height);
    }
}

- (void)refreshOffset
{
    if (self.layout.vertical)
    {
        _scroll.contentOffset = CGPointMake(0, _currentScrollIndex * _scroll.height);
    }
    else
    {
        _scroll.contentOffset = CGPointMake(_currentScrollIndex * _scroll.width, 0);
    }
}

- (void)gotoIndex:(NSInteger)index
{
    [self loadIndex:[self.dataSource valideIndexForIndex:index]];
}

- (void)loadIndex:(NSInteger)index
{
    _currentIndex = index;
    NSInteger offset = 0;
    NSArray<UIView *> * pageList = [self.dataSource configPageListForIndex:_currentIndex offset:&offset];
    [self layoutPages:pageList offset:offset];
}

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    if (self.openFadeEffect && _effectImgView.alpha > 0)
    {
        [UIView animateWithDuration:.13 delay:0 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionBeginFromCurrentState animations:^{
            _effectImgView.alpha = 0;
        } completion:nil];
    }
    self.pageWithFade = NO;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    NSInteger offsetAmount = self.layout.vertical ? scrollView.contentOffset.y : scrollView.contentOffset.x;
    CGFloat pageAmount = self.layout.vertical ? CGRectGetHeight(scrollView.frame) : CGRectGetWidth(scrollView.frame);
    NSInteger index = _currentIndex;
    if (offsetAmount >= (_currentScrollIndex + 1) * pageAmount)
    {
        index = _currentIndex + 1;
    }
    else if (offsetAmount <= (_currentScrollIndex - 1) * pageAmount)
    {
        index = _currentIndex - 1;
    }
    if (_currentIndex != index)
    {
        if ([self.delegate respondsToSelector:@selector(cycleView:didEndDisplayPage:atIndex:)])
        {
            [self.delegate cycleView:self didEndDisplayPage:[self currentPage] atIndex:_currentIndex];
        }
        index = [self.dataSource valideIndexForIndex:index];
        [self loadIndex:index];
        if ([self.delegate respondsToSelector:@selector(cycleView:didMoveToIndex:)])
        {
            [self.delegate cycleView:self didMoveToIndex:index];
        }
    }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self refreshOffset];
}

- (void)layoutPages:(NSArray<UIView *> *)pages offset:(NSInteger)offset
{
    _currentScrollIndex = offset;
    for (NSInteger index = 0; index < pages.count; index++)
    {
        UIView * page = pages[index];
        page.hidden = NO;
        if (page.khScrollIndex != index)
        {
            CGRect frame = self.layout.vertical ? CGRectOffset(_layout.pageFrame, 0, index * _scroll.height) : CGRectOffset(_layout.pageFrame, index * _scroll.width, 0);
            page.frame = frame;
            [_scroll addSubview:page];
            if (page.constraints.count > 0)
            {
                [page mas_updateConstraints:^(MASConstraintMaker *make) {
                    make.left.equalTo(frame.origin.x);
                    make.top.equalTo(frame.origin.y);
                    make.width.equalTo(frame.size.width);
                    make.height.equalTo(frame.size.height);
                }];
            }
            page.khScrollIndex = index;
        }
    }
    [self refreshOffset];
}

- (void)setAutoCycleInterval:(NSTimeInterval)autoCycleInterval
{
    _autoCycleInterval = autoCycleInterval;
    [self stopAutoCycle];
    if (autoCycleInterval > 0)
    {
        [self startAutoCycle];
    }
}

- (void)stopAutoCycle
{
    [_autoTimer invalidate];
    _autoTimer = nil;
}

- (void)startAutoCycle
{
    if (![_autoTimer isValid])
    {
        _autoTimer = [NSTimer timerWithTimeInterval:_autoCycleInterval target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
        [[NSRunLoop mainRunLoop] addTimer:_autoTimer forMode:NSRunLoopCommonModes];
    }
}

- (void)scrollPan:(UIGestureRecognizer *)gesutre
{
    if (gesutre.state == UIGestureRecognizerStateBegan)
    {
        [self stopAutoCycle];
    }
    else if (gesutre.state == UIGestureRecognizerStateEnded)
    {
        if (_autoCycleInterval > 0)
        {
            [self startAutoCycle];
        }
    }
}

- (void)nextPage
{
    if (!self.loopEnabled)
    {
        return;
    }
    BOOL nextPageIsValid = NO;
    if (self.layout.vertical)
    {
        nextPageIsValid = roundf((_currentScrollIndex + 1) * _scroll.height) <= roundf(_scroll.contentSize.height);
    }
    else
    {
        nextPageIsValid = roundf((_currentScrollIndex + 1) * _scroll.width) <= roundf(_scroll.contentSize.width);
    }
    if (nextPageIsValid)
    {
        BOOL doAnimation = NO;
        if (self.openFadeEffect)
        {
            UIImage * fromImage = [self imageFromPage:[self currentPage]];
            UIImage * toImage = [self imageFromPage:[[self dataSource] pageAtIndex:[self.dataSource valideIndexForIndex:_currentIndex + 1]]];
            if (fromImage != nil && toImage != nil)
            {
                doAnimation = YES;
                CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"contents"];
                animation.delegate = self;
                animation.removedOnCompletion = YES;
                animation.duration = 1;
                animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
                animation.fromValue = (__bridge id _Nullable)(fromImage.CGImage);
                animation.toValue = (__bridge id _Nullable)(toImage.CGImage);
                //            animation.fillMode = kCAFillModeBoth;
                [_effectImgView.layer addAnimation:animation forKey:@"FadeEffect"];
            }
        }
        if (!doAnimation)
        {
            if (self.layout.vertical)
            {
                [_scroll setContentOffset:CGPointMake(0, (_currentScrollIndex + 1) * _scroll.height) animated:YES];
            }
            else
            {
                [_scroll setContentOffset:CGPointMake((_currentScrollIndex + 1) * _scroll.width, 0) animated:YES];
            }
        }
    }
}

- (UIImage *)imageFromPage:(UIView *)page
{
    if ([page isKindOfClass:[UIImageView class]])
    {
        return [(UIImageView *)page image];
    }
    return [THBaseUtil imageFromView:page];
}

- (void)animationDidStart:(CAAnimation *)anim
{
    _effectImgView.alpha = 1;
    self.pageWithFade = YES;
    if (self.layout.vertical)
    {
        [_scroll setContentOffset:CGPointMake(0, (_currentScrollIndex + 1) * _scroll.height) animated:NO];
    }
    else
    {
        [_scroll setContentOffset:CGPointMake((_currentScrollIndex + 1) * _scroll.width, 0) animated:NO];
    }
}

//- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
//{
//    _effectImgView.alpha = 0;
//}

- (UIView *)currentPage
{
    for (UIView * page in self.dataSource.localPageList)
    {
        if (page.khPageIndex == _currentIndex)
        {
            return page;
        }
    }
    return nil;
}

- (void)willMoveToSuperview:(UIView *)newSuperview
{
    if (!newSuperview)
    {
        _scroll.delegate = nil;
        [_autoTimer invalidate];
    }
    else
    {
        _scroll.delegate = self;
    }
}

- (UIView *)pageAtIndex:(NSInteger)index
{
    if (index < [self.delegate numberOfPagesInCycleView:self])
    {
        return [self.dataSource pageAtIndex:index];
    }
    return nil;
}

- (void)openFadeEffect:(BOOL)open
{
    self.openFadeEffect = open;
    if (open)
    {
        self.clipsToBounds = YES;
    }
}

- (void)openFadeEffect:(BOOL)open contentMode:(UIViewContentMode)contentMode
{
    [self openFadeEffect:open];
    self.effectImgView.contentMode = contentMode;
}

- (BOOL)loopEnabled
{
    return self.layout.loopEnabled && self.dataSource.totalCount > 1;
}

@end
